package edu.ccut.saturn.component.impl.node;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.jexl.ExpressionFactory;

import edu.ccut.saturn.component.ComponentManager;
import edu.ccut.saturn.component.ILogicContext;
import edu.ccut.saturn.component.IParameterContext;
import edu.ccut.saturn.component.IParameterInfo;
import edu.ccut.saturn.component.SaturnComponentException;
import edu.ccut.saturn.component.SaturnDebugThreadsManager;
import edu.ccut.saturn.component.impl.SaturnParameterInfo;
import edu.ccut.saturn.component.loader.node.NodeInfoLoader;
import edu.ccut.saturn.component.loader.node.SwitchNodeInfoLoader;
import edu.ccut.saturn.component.utils.ExceptionUtils;
import edu.ccut.saturn.component.xml.ISaturnElement;
import edu.ccut.saturn.component.xml.SaturnElement;

/**
 * 分支结点的实现类。在logic组件中调用分支结点，分支结点拥有多个分支连线连接其它结点；
 * 每个分支记录一个表达式，当判断表达式为真时运行时的下一结点就是这个分支所连接的结点。
 * 提供了一个默认分支，当所有表达式都为假时下一结点为默认分支连接的结点。
 * <p>
 * 类<code>SwitchNodeInfo</code>扩展了<code>AbstractNodeInfo</code>：
 * <li>字段<code>branches</code>：记录了每个分支的表达式及连接的结点id的信息；
 * <li>字段<code>defaultNode</code>：记录了默认结点的id。
 * 
 * @author 温建
 * 
 * @version 1.0 2008.8.21
 * 
 * @see AbstractNodeInfo
 * @see ILogicContext
 * @see IParameterContext
 * 
 */
public class SwitchNodeInfo extends AbstractNodeInfo {

	private static final String NEXT = "next";

	private static final String EXPR = "expr";

	private List<Map<String, Object>> branches;

	private String defaultNode;

	/**
	 * 获得记录分支的表达式及结点的信息的列表
	 * 
	 * @return
	 */
	public List<Map<String, Object>> getBranches() {
		return branches;
	}

	/**
	 * 设置记录分支的表达式及结点的信息的列表
	 * 
	 * @param branches
	 */
	public void setBranches(List<Map<String, Object>> branches) {
		this.branches = branches;
	}

	/**
	 * 获得默认的下一结点的id
	 * 
	 * @return
	 */
	public String getDefaultNode() {
		return defaultNode;
	}

	/**
	 * 设置默认的下一结点的id
	 * 
	 * @param defaultNode
	 */
	public void setDefaultNode(String defaultNode) {
		this.defaultNode = defaultNode;
	}

	public SwitchNodeInfo(String id, String name, String description,
			String nextId, List<IParameterInfo> parameters,
			List<Map<String, Object>> branches, String defaultNode, String x,
			String y) {

		// TODO:nextId设置为空
		super(id, name, description, nextId, parameters, x, y, "switch");
		this.branches = branches;
		this.defaultNode = defaultNode;
	}

	/**
	 * 分支结点的运行逻辑。 循环判断每个分支的表达式，当表达式为空时判断结果为假；当表达式为真时当前结点的下一结点
	 * 就是这个分支连接的结点。直到某个分支的表达式为真时，循环结束不再判断其它分支的表达式；
	 * 或者判断所有的表达式后结果都为假，这时默认结点将作为当前结点的下一个运行结点。 TODO: 逻辑是否正确？
	 * 
	 * @param logicContext
	 *            逻辑上下文对象，记录结点运行时的上下文信息
	 * @throws Exception
	 */
	@Override
	public void doExecute(ILogicContext logicContext) throws Exception {
		ComponentManager.beforeExecuteComponent(this, this
				.createParameterContext(logicContext));

		try {

			if (branches != null && branches.size() > 0) {
				boolean isRight = false;
				String nodeId = defaultNode;

				for (Map<String, Object> branch : branches) {

					if (isRight == true) {
						break;
					}
					String expr = (String) branch.get(EXPR);

					if (expr == null || expr.length() == 0) {
						isRight = false;

					} else {
						Object result = ExpressionFactory
								.createExpression(expr).evaluate(
										logicContext.getJexlContext());

						if (result instanceof Boolean && (Boolean) result) {
							isRight = true;
							nodeId = (String) branch.get(NEXT);

						} else {
							isRight = false;
						}
					}

					logger.debug("      node : {execute : '{expression : '" + expr + "', result : '"
							+ isRight + "'}', process : '" + logicContext.getId() + "'}");
				}

				if (nodeId == null || "".equals(nodeId)) {
					logger
							.error("Switch Node execution is over! no branch will be execute, please set the default branch for this question.");

					throw new SaturnComponentException("The switch node ["
							+ this.getId()
							+ "] can not decide next node to be executed! ");

				} else {
					logicContext.setNodeRouter(this.getId(), nodeId);
				}
			}
		} catch (Exception e) {
			logger.error("ERROR : {message : '" + ExceptionUtils.getExMessage(e) + "', process : '" + logicContext.getId() + "', node : '"
					+ super.getNodeType() + " " + super.getId() + " " + super.getName()+ "'}");
			throw e;
			
		} finally {
			ComponentManager.afterExecuteComponent(this, this
					.createParameterContext(logicContext));
		}
	}

	protected IParameterContext createParameterContext(
			ILogicContext logicContext) throws Exception {

		if (SaturnDebugThreadsManager.getInstance().isStart()) {
			initParameterInfos(logicContext);
			return logicContext.createParameterContext(getParameterInfos());
		}
		return null;
	}

	private void initParameterInfos(ILogicContext context) {
		int parameterIndex = 1;
		List<IParameterInfo> parameters = new ArrayList<IParameterInfo>();

		for (Map<String, Object> branch : branches) {
			SaturnParameterInfo inParameterInfo = new SaturnParameterInfo();
			inParameterInfo.setClassType(String.class);
			inParameterInfo.setName("branch_" + parameterIndex);
			inParameterInfo.setId(String.valueOf(parameterIndex));
			inParameterInfo.setValueKey(branch.get(EXPR).toString());
			inParameterInfo.setScope(IParameterInfo.SCOPE_LOGIC);
			inParameterInfo.setPassingType(IParameterInfo.PASSING_IN);
			parameters.add(inParameterInfo);
			parameterIndex++;
		}
		SaturnParameterInfo resultParameterInfo = new SaturnParameterInfo();
		resultParameterInfo.setClassType(String.class);
		resultParameterInfo.setName("next node");
		resultParameterInfo.setId(String.valueOf(parameterIndex));
		resultParameterInfo.setValueKey("'"
				+ context.getNextNodeId(this.getId()) + "'");

		resultParameterInfo.setScope(IParameterInfo.SCOPE_LOGIC);
		resultParameterInfo.setPassingType(IParameterInfo.PASSING_IN_OUT);
		parameters.add(resultParameterInfo);
		super.setParameterInfos(parameters);
	}

	public ISaturnElement toSaturnElement() {
		ISaturnElement switchElement = new SaturnElement(
				SwitchNodeInfoLoader.KEY_NODETYPE_SWITCH);

		switchElement.addAttribute(NodeInfoLoader.KEY_NODE_ID, this.getId());
		switchElement.addAttribute(NodeInfoLoader.KEY_NODE_X, this.getX());
		switchElement.addAttribute(NodeInfoLoader.KEY_NODE_Y, this.getY());
		switchElement.addAttribute(NodeInfoLoader.KEY_NODE_NEXT, this
				.getNextId());

		ISaturnElement nameElement = new SaturnElement(
				NodeInfoLoader.KEY_NODE_NAME, this.getName());

		ISaturnElement descriptionElement = new SaturnElement(
				NodeInfoLoader.KEY_NODE_DESCRIPTION, this.getDescription());

		if (this.branches != null) {

			for (Map<String, Object> branch : this.branches) {
				ISaturnElement branchElement = new SaturnElement(
						SwitchNodeInfoLoader.KEY_SWITCH_IF);

				branchElement.addAttribute(
						SwitchNodeInfoLoader.KEY_SWITCH_IF_THEN, branch.get(
								NEXT).toString());

				if (branch.get(NEXT).toString().equals(this.defaultNode)) {
					branchElement.addAttribute(
							SwitchNodeInfoLoader.KEY_SWITCH_IF_ISDEFAULT,
							"true");
				}
				ISaturnElement exprElement = new SaturnElement(
						SwitchNodeInfoLoader.KEY_SWITCH_IF_EXPR, branch.get(
								EXPR).toString());

				branchElement.addSubElement(exprElement);
				switchElement.addSubElement(branchElement);
			}
		}
		switchElement.addSubElement(nameElement);
		switchElement.addSubElement(descriptionElement);
		return switchElement;
	}
}